#!/usr/bin/env python

# pita: An invisible audio player
# Copyright (C) 2006-2007 Austin Bingham
#
# This program is free software# you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation# either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY# without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program# if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
# You can reach the author at:
#     austin.bingham@gmail.com

import logging, sys
import pita.app, pita.m3u
import pita.client_util as cutil

logger = logging.getLogger('pita')

def add_songs(songs, replace=False):
    '''
    Given a list of files, this checks to see if they appear
    valid and, if so, passes them to the server. If replace is
    True, then the server is cleared before sending the new list
    and it is unpaused after the new list is sent.
    '''
    server = cutil.get_server_proxy_or_die(start_new=True)
    good,bad = cutil.fix_and_test_paths(pita.m3u.expand_filelist(songs))

    if len(bad) > 0:
        for b in bad:
            logger.warning('Unable to read file %s' % b)
    else:
        if replace:
            logger.info('clearing server')
            server.clear()
            
        server.append(good)

        if replace:
            server.unpause()
            logger.info('unpausing player')

def jump_to(position):
    server = cutil.get_server_proxy_or_die()
    server.jump_to(int(position))

def remove(position):
    server = cutil.get_server_proxy_or_die()
    server.remove(int(position))

def get_playlist():
    server = cutil.get_server_proxy_or_die()
    playlist = cutil.net_to_playlist(server.get_playlist())
    current = playlist.current

    if len(playlist) < 1: return
    
    for idx in range(len(playlist)):
        prefix = ' '
        if idx == current: prefix = '*'
        print prefix,idx,'\t',playlist[idx]

def get_status():
    server = cutil.get_server_proxy_or_die()
    playlist = cutil.net_to_playlist(server.get_playlist())
    if len(playlist) < 1:
        print 'NO SONGS IN QUEUE'
    else:
        print '[QUEUE_SIZE]\t',len(playlist)
        print '[CURRENT_INDEX]\t',playlist.current
        if playlist.current < len(playlist):
            print '[CURRENT_FILE]\t',playlist[playlist.current]
    print '[PAUSED]\t',server.is_paused()
    print '[RANDOM]\t',server.is_random()
    print '[LOOPING]\t',server.is_looping()
    print '[SAMPLE_RATE]\t',server.sample_rate()

def get_paused():
    server = cutil.get_server_proxy_or_die()
    print server.is_paused()

def set_random(r):
    server = cutil.get_server_proxy_or_die()
    server.set_random(r)

def set_looping(l):
    server = cutil.get_server_proxy_or_die()
    server.set_looping(l)

def set_sample_rate(rate):
    server = cutil.get_server_proxy_or_die()

    if rate[0] == '~':
        rate = server.sample_rate() - int(rate[1:])
    elif rate[0] == '+':
        rate = server.sample_rate() + int(rate)
    else:
        rate = int(rate)
        
    server.set_sample_rate(rate)

def get_sample_rate():
    server = cutil.get_server_proxy_or_die()
    print server.sample_rate()

def server_command(command):
    server = cutil.get_server_proxy_or_die()
    getattr(server, command)()

class Command:
    '''
    This captures the important details of a
    particular command that the user can call in
    pita.
    '''
    commands = []
    
    def __init__(self,
                 cmd,
                 func,
                 description='',
                 usage=None):
        '''
        @type cmd: string
        @param cmd: the name of the command
        @type func: callable(arguments)
        @param func: the function called in response to this command
        @type description: string
        @param description: A description of the command (meant to be read by users)
        @type usage: string
        @param usage: the usage-string for this command (defaults to cmd)
        '''
        self.cmd = cmd
        self.func = func
        if usage:
            self.usage = usage
        else:
            self.usage = cmd
        self.description = description

        Command.commands.append(self)

    def __call__(self, *args):
        '''
        @type args: sequence
        @param args: the other arguments to pass to the function
        
        calls the commands associated function with args as
        arguments.
        '''
        self.func(*args)

    @staticmethod
    def find(cmd_name):
        '''
        @type cmd_name: string
        @param cmd_name: name of the command to search for
        @return: the command referred to by cmd_name
        
        searchs the command list for a command named cmd_name.
        The search also attempts to use prefix-matching. The algorithm is:
        - if cmd_name matches any command exactly, that is returned
        - if cmd_name matches the beginning of exactly one command,
          that command is returned
        - otherwise, KeyError is raised
        
        This throws KeyError if there is no matching command.
        '''
        matches = []
        for cmd in Command.commands:
            if cmd.cmd == cmd_name:
                return cmd
            elif cmd.cmd.startswith(cmd_name):
                matches.append(cmd)

        if len(matches) == 1:
            return matches[0]
        
        raise KeyError

Command('next',
        lambda : server_command('next'),
        'play next song in playlist')
Command('previous',
        lambda : server_command('previous'),
        'play previous song in playlist')
Command('jump-to',
        jump_to,
        'jump to position in playlist',
        'jump-to <position>')
Command('pause',
        lambda : server_command('pause'),
        'pause the player')
Command('unpause',
        lambda : server_command('unpause'),
        'unpause the player')
Command('paused',
        get_paused,
        'prints the pause status of the player')
Command('pause-play',
        lambda : server_command('pause_play'),
        'toggles the pause-status of the player')
Command('add',
        add_songs,
        'appends a series of filenames to the playlist',
        'add [filename . . .]')
Command('remove',
        remove,
        'removes the file at <position> from the playlist',
        'remove <position>')
Command('clear',
        lambda : server_command('clear'),
        'clears the playlist (and stops the player')
Command('kill',
        lambda : server_command('kill'),
        'terminates the player daemon')
Command('playlist',
        get_playlist,
        'prints the current playlist')
Command('random',
        lambda : set_random( True),
        'puts the player in random mode')
Command('unrandom',
        lambda : set_random( False),
        'takes the player out of random mode')
Command('loop',
        lambda : set_looping( True),
        'puts the player in looping mode')
Command('unloop',
        lambda : set_looping( False),
        'takes the player out of looping mode')
Command('set-sample-rate',
        set_sample_rate,
        'sets the sample rate for the player (samples/sec)',
        'set-sample-rate [~+]<sample-rate>')
Command('sample-rate',
        get_sample_rate,
        'print the current sample rate')
Command('status',
        get_status,
        'prints general info on the status of the player')

class Application(pita.app.ClientApplication):
    def __init__(self):
        pita.app.ClientApplication.__init__(self, 'pita')

    def usage(self):
        usage = """usage: %prog [options] command [args]
       %prog [options] filename . . .

commands:\n"""

        usage += '\n'.join(['  %s' % cmd.usage for cmd in Command.commands])
        return usage

    def run(self, args):
        if len(args) < 1:
            logger.error('no command provided')
            sys.exit(0)

        cmd_name = args[0]
        cmd_args = args[1:]

        try:
            Command.find(cmd_name)(*cmd_args)
            logger.info('%s %s' % (cmd_name, cmd_args))
        except KeyError:
            add_songs(args, True) # replace current playlist

if __name__ == '__main__':
    app = Application()
    app.start()
